﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using System.IO;
using System.Xml;
using Innovox.NetTools;

namespace Innovox.YDeviceLib
{
    public enum ObixTypes
	{
		Obj,
		Val,
		List,
		Op,
		Feed,
		Href,
		Err,
		Bool,
		Int,
		Real,
		Str,
		Enum,
		Abstime,
		Reltime,
		Uri
	}

    public enum ObixRequest
    {
        Read,
        Write,
        Invoke
    }

    public class ObixEntry
    {
        public string Uri{get;set;}
        public string Value{get;set;}

        public ObixEntry(string uri, string value)
        {
            Uri = uri;
            Value = value;
        }
    }

	public class ObixObject : ICloneable
	{
        static public Dictionary<string, int> AutoModeDict { get; set; }
        static public Dictionary<string, ObixObject> ObjectDict { get; set; }
 
        static ObixObject()
        {
            AutoModeDict = new Dictionary<string, int>();
            AutoModeDict.Add("man", 1);
            AutoModeDict.Add("autoOnOff",2);
            AutoModeDict.Add("manOnAutoOff", 3);
            ObjectDict = new Dictionary<string, ObixObject>();
            ObjectDict.Add("/obix/firmwareVersion/", new ObixObject("str", "firmwareVersion", "", "/obix/firmwareVersion/"));
            ObjectDict.Add("/obix/about/serverName/", new ObixObject("str", "serverName", "", "/obix/about/serverName/"));
            ObjectDict.Add("/obix/status/zcdFreq/", new ObixObject("int", "zcdFreq", "", "/obix/status/zcdFreq/"));
            ObjectDict.Add("/obix/name/", new ObixObject("str", "name", "", "/obix/name/"));
            ObjectDict.Add("/obix/location/", new ObixObject("str", "name", "", "/obix/location/"));
            ObjectDict.Add("/obix/y1g/adv/ledColor/", new ObixObject("enum", "ledColor", "", "/obix/y1g/adv/ledColor/"));
            ObjectDict.Add("/obix/y1g/pirSens/", new ObixObject("int", "pirSens", "", "/obix/y1g/pirSens/"));
            ObjectDict.Add("/obix/y1g/adv/motionMuteDelay/", new ObixObject("int", "motionMuteDelay", "", "/obix/y1g/adv/motionMuteDelay/"));
            ObjectDict.Add("/obix/y1g/offDelay/", new ObixObject("int", "offDelay", "", "/obix/y1g/offDelay/"));
            ObjectDict.Add("/obix/y1g/adv/lumFactor/", new ObixObject("int", "lumFactor", "", "/obix/y1g/adv/lumFactor/"));
            ObjectDict.Add("/obix/y1g/adv/flickReps/", new ObixObject("int", "flickReps", "", "/obix/y1g/adv/flickReps/"));
            ObjectDict.Add("/obix/y1g/autoMode/", new ObixObject("enum", "autoMode", "", "/obix/y1g/autoMode/"));
            ObjectDict.Add("/obix/y1g/adv/switchStatus/", new ObixObject("enum", "switchStatus", "", "/obix/y1g/adv/switchStatus/"));
            ObjectDict.Add("/obix/y1g/adv/networkOn/", new ObixObject("bool", "networkOn", "", "/obix/y1g/adv/networkOn/"));
            ObjectDict.Add("/obix/y1g/adv/buttonPressed/", new ObixObject("bool", "buttonPressed", "", "/obix/y1g/adv/buttonPressed/"));
            ObjectDict.Add("/obix/y1g/adv/lumSens/", new ObixObject("int", "lumSens", "", "/obix/y1g/adv/lumSens/"));
            ObjectDict.Add("/obix/y1g/adv/luminosity/", new ObixObject("int", "luminosity", "", "/obix/y1g/adv/luminosity/"));
            ObjectDict.Add("/obix/y1g/noMotionTime/", new ObixObject("int", "noMotionTime", "", "/obix/y1g/noMotionTime/"));
            ObjectDict.Add("/obix/y1g/bPressLapse/", new ObixObject("int", "bPressLapse", "", "/obix/y1g/bPressLapse/"));
            
            ObjectDict.Add("/obix/y1g/latchActive/", new ObixObject("bool", "latchActive", "", "/obix/y1g/latchActive/"));
            ObjectDict.Add("/obix/y1g/flickWarn/", new ObixObject("bool", "flickWarn", "", "/obix/y1g/flickWarn/"));

            ObjectDict.Add("/obix/yfm/buttonPressed/", new ObixObject("obj", "buttonPressed", "", "/obix/yfm/buttonPressed/"));
            ObjectDict.Add("/obix/yfm/input/", new ObixObject("bool", "input", "", "/obix/yfm/input/"));
            ObjectDict.Add("/obix/yfm/noMotionTime/", new ObixObject("int", "noMotionTime", "", "/obix/yfm/noMotionTime/"));
            //ObjectDict.Add("/obix/yfm/input/", new ObixObject("bool", "input", "", "/obix/yfm/input/"));
            ObjectDict.Add("/obix/yfm/bPressLapse/", new ObixObject("int", "bPressLapse", "", "/obix/yfm/bPressLapse/"));
            ObjectDict.Add("/obix/yfm/dimming/", new ObixObject("obj", "dimming", "", "/obix/yfm/dimming/"));
            ObjectDict.Add("/obix/yfm/dimming/dimMode/", new ObixObject("enum", "dimMode", "", "/obix/yfm/dimming/dimMode/"));
            ObjectDict.Add("/obix/yfm/dimming/dim/", new ObixObject("int", "dim", "", "/obix/yfm/dimming/dim/"));
            ObjectDict.Add("/obix/yfm/dimming/dimMin/", new ObixObject("int", "dimMin", "", "/obix/yfm/dimming/dimMin/"));
            ObjectDict.Add("/obix/yfm/motionMuteDelay/", new ObixObject("int", "motionMuteDelay", "", "/obix/yfm/motionMuteDelay/"));

		    ObjectDict.Add("/obix/yfm/relay1/", new ObixObject("obj", "relay1", "", "/obix/yfm/relay1/"));
            ObjectDict.Add("/obix/yfm/relay1/offDelay/", new ObixObject("int", "offDelay", "", "/obix/yfm/relay1/offDelay/"));
            ObjectDict.Add("/obix/yfm/relay1/flickReps/", new ObixObject("int", "flickReps", "", "/obix/yfm/relay1/flickReps/"));
            ObjectDict.Add("/obix/yfm/relay1/autoMode/", new ObixObject("enum", "autoMode", "", "/obix/yfm/relay1/autoMode/"));
            ObjectDict.Add("/obix/yfm/relay1/switchStatus/", new ObixObject("enum", "switchStatus", "", "/obix/yfm/relay1/switchStatus/"));
            ObjectDict.Add("/obix/yfm/relay1/latchActive/", new ObixObject("bool", "latchActive", "", "/obix/yfm/relay1/latchActive/"));
            ObjectDict.Add("/obix/yfm/relay1/flickWarn/", new ObixObject("bool", "flickWarn", "", "/obix/yfm/relay1/flickWarn/"));

			ObjectDict.Add("/obix/yfm/relay2/", new ObixObject("obj", "relay2", "", "/obix/yfm/relay2/"));
            ObjectDict.Add("/obix/yfm/relay2/offDelay/", new ObixObject("int", "offDelay", "", "/obix/yfm/relay2/offDelay/"));
            ObjectDict.Add("/obix/yfm/relay2/flickReps/", new ObixObject("int", "flickReps", "", "/obix/yfm/relay2/flickReps/"));
            ObjectDict.Add("/obix/yfm/relay2/autoMode/", new ObixObject("enum", "autoMode", "", "/obix/yfm/relay2/autoMode/"));
            ObjectDict.Add("/obix/yfm/relay2/switchStatus/", new ObixObject("enum", "switchStatus", "", "/obix/yfm/relay2/switchStatus/"));
            ObjectDict.Add("/obix/yfm/relay2/latchActive/", new ObixObject("bool", "latchActive", "", "/obix/yfm/relay2/latchActive/"));
            ObjectDict.Add("/obix/yfm/relay2/flickWarn/", new ObixObject("bool", "flickWarn", "", "/obix/yfm/relay2/flickWarn/"));

			ObjectDict.Add("/obix/yfm/relay3/", new ObixObject("obj", "relay3", "", "/obix/yfm/relay3/"));
            ObjectDict.Add("/obix/yfm/relay3/offDelay/", new ObixObject("int", "offDelay", "", "/obix/yfm/relay3/offDelay/"));
            ObjectDict.Add("/obix/yfm/relay3/flickReps/", new ObixObject("int", "flickReps", "", "/obix/yfm/relay3/flickReps/"));
            ObjectDict.Add("/obix/yfm/relay3/autoMode/", new ObixObject("enum", "autoMode", "", "/obix/yfm/relay3/autoMode/"));
            ObjectDict.Add("/obix/yfm/relay3/switchStatus/", new ObixObject("enum", "switchStatus", "", "/obix/yfm/relay3/switchStatus/"));
            ObjectDict.Add("/obix/yfm/relay3/latchActive/", new ObixObject("bool", "latchActive", "", "/obix/yfm/relay3/latchActive/"));
            ObjectDict.Add("/obix/yfm/relay3/flickWarn/", new ObixObject("bool", "flickWarn", "", "/obix/yfm/relay3/flickWarn/"));

			ObjectDict.Add("/obix/yfm/relay4/", new ObixObject("obj", "relay4", "", "/obix/yfm/relay4/"));
            ObjectDict.Add("/obix/yfm/relay4/offDelay/", new ObixObject("int", "offDelay", "", "/obix/yfm/relay4/offDelay/"));
            ObjectDict.Add("/obix/yfm/relay4/flickReps/", new ObixObject("int", "flickReps", "", "/obix/yfm/relay4/flickReps/"));
            ObjectDict.Add("/obix/yfm/relay4/autoMode/", new ObixObject("enum", "autoMode", "", "/obix/yfm/relay4/autoMode/"));
            ObjectDict.Add("/obix/yfm/relay4/switchStatus/", new ObixObject("enum", "switchStatus", "", "/obix/yfm/relay4/switchStatus/"));
            ObjectDict.Add("/obix/yfm/relay4/latchActive/", new ObixObject("bool", "latchActive", "", "/obix/yfm/relay4/latchActive/"));
            ObjectDict.Add("/obix/yfm/relay4/flickWarn/", new ObixObject("bool", "flickWarn", "", "/obix/yfm/relay4/flickWarn/"));

        }

        public static Dictionary<ObixTypes, string> ObixTypesNames = new Dictionary<ObixTypes, string>() {
            {ObixTypes.Obj,"obj"}, {ObixTypes.Val,"val"}, {ObixTypes.List,"list"}, {ObixTypes.Op,"op"},
		    {ObixTypes.Feed,"feed"}, {ObixTypes.Href,"href"}, {ObixTypes.Err,"err"}, {ObixTypes.Bool,"bool"},
		    {ObixTypes.Int,"int"}, {ObixTypes.Real,"real"},	{ObixTypes.Str,"str"}, {ObixTypes.Enum,"enum"},
		    {ObixTypes.Abstime,"abstime"}, {ObixTypes.Reltime,"reltime"}, {ObixTypes.Uri,"uri"}};

        public string Type { get; set; }
        public string Name { get; set; }
		public string Value { get; set; }
		public string Uri { get; set; }
        public string Contract { get; set; }
        public ObixRequest Request { get; set; }
        
		public ObixObject()
		{
            Type = null; // ObixTypes.Obj;
			Name = Value = Uri = Contract = null;
		}

		public ObixObject(string type, string name, string val, string uri)
		{
			Type = type;
			Name = name;
			Value = val;
			Uri = uri;
		}

        public object Clone()
        {
            return this.MemberwiseClone(); 
        }  
      
        public ObixObject Create(string uri)
        {
            try
            {
                ObixObject o = (ObixObject) ObjectDict[uri].Clone();
                return o;
            }
            catch (Exception e)
            {
                return null;
            }
        }

        public static string[] FindValueAttribute(string[] uris, List<ObixObject> objs)
        {
            string[] results = null;

            if (objs != null)
            {
                results = new string[uris.Length];

                for (int i = 0; i < uris.Length; i++)
                    foreach (ObixObject o in objs)
                    {
                        //if (o.Uri.CompareTo(uris[i]) == 0)
                        if (o.Uri.Contains(uris[i]))
                        {
                            results[i] = o.Value;
                            break;
                        }
                    }
            }
            return results;
        }
	}

    public class ObixClient
    {
        public IPAddress Ip { get; private set; }
        public int Port { get; set; }
        List<ObixObject> _batchInObjects;
        List<ObixObject> _batchOutObjects;

        public ObixClient(IPAddress ip, int port)
        {
            Ip = ip;
            Port = port;
            _batchInObjects = null;
            _batchOutObjects = null;
        }

        private string BuildBatchBody(List<ObixObject> objects)
        {
            // Build Post body
            string body = "<list of=\"obix:uri\" is=\"obix:BatchIn\">\r\n";

			foreach (ObixObject ob in objects)
            {
                if(ob.Request == ObixRequest.Write)
                {
                    body += "<uri is=\"obix:Write\" ";
                    body += "val=\"" + ob.Uri + "\">\r\n";
                    body += "<" + ob.Type +  " name=\"in\" val=\"" + ob.Value + "\" />\r\n</uri>\r\n";
                }
				else if (ob.Request == ObixRequest.Read)
				{
					body += "<uri is=\"obix:Read\" ";
					body += "val=\"" + ob.Uri + "\"/>\r\n";
				}
            }
            body += "</list>\r\n";

            return body;
        }

        public List<string> ErrorMessages;

        private void AddErrorMessage(string error)
        {
            if (ErrorMessages == null)
                ErrorMessages = new List<string>();

            ErrorMessages.Add(error);
        }

        private void ClearErrorMessages()
        {
            if (ErrorMessages != null)
                ErrorMessages.Clear();
        }

        public string GetErrorMessage()
        {
            if (ErrorMessages == null || ErrorMessages.Count == 0)
                return "";

            string error = "Error at Ip: " + Ip.ToString() + "\r\n";

            foreach (string s in ErrorMessages)
                error += s + "\r\n";
            return error;
        }

        public List<ObixObject> ParseBatchOut(string body)
        {            
            List<ObixObject> outObje = new List<ObixObject>();
            XmlDocument xdoc = new XmlDocument();
            xdoc.LoadXml(body);
            XmlNodeList elemList = xdoc.GetElementsByTagName("list");

            if (elemList.Count == 1)
            {
                foreach (XmlNode n in elemList[0].ChildNodes)
                {
                    ObixObject o = new ObixObject();
                    o.Type = n.Name;
                    if (ObixObject.ObixTypesNames.ContainsValue(n.Name))
                    {
                        //o.Type = ObixObject.ObixTypesNames.g //o.Name;
                        //Console.WriteLine(n.Name);
                        string displayError = "";
                        foreach (XmlAttribute attrib in n.Attributes)
                        {
                            if (attrib.Name == "href")
                                o.Uri = attrib.Value;
                            else if (attrib.Name == "val")
                                o.Value = attrib.Value;
                            else if (attrib.Name == "is")
                                o.Contract = attrib.Value;
                            else if (attrib.Name == "display")
                                displayError = attrib.Value;
                           // Console.WriteLine(attrib.Name + " " + attrib.Value);
                        }
                        if (o.Type == "err")
                        {
                            string error;
                            if( displayError == "")
                                error = "href: " + o.Uri + " " + o.Contract;
                            else
                                error = "href: " + o.Uri + " " + o.Contract + ": " + displayError;
                            AddErrorMessage(error);
                        }
                    }
                    outObje.Add(o);
                }
            }

            if( outObje == null)
                AddErrorMessage(Ip.ToString() + ": Connection error");

            return outObje;
        }

        /// <summary>
        /// Send a Batch invoke to a specific obix server
        /// </summary>
        /// <param name="batchIn"></param>
        /// <returns></returns>
        public List<ObixObject> SendBatch(List<ObixObject> batchIn)
        {
            ClearErrorMessages();
            _batchInObjects = batchIn;

            //try
            {
                string postBody = BuildBatchBody(batchIn);
                // Create a request using a URL that can receive a post. 
                string url = "http://" + Ip.ToString() + ":" + Port.ToString() + "/obix/batch/";
                HttpRequest request = new HttpRequest(url);
                request.Post(postBody);
                string response = request.ResponseToString();
				_batchOutObjects = ParseBatchOut(response);
            }
            /*catch (Exception)
            {
                string url = "http://" + Ip.ToString() + ":" + Port.ToString() + "/obix/batch/";

                AddErrorMessage("Can't connect to " + url);
            }*/
            return _batchOutObjects;
        }

        public ObixObject Get(ObixObject inObject)
        {
            ClearErrorMessages();

            ObixObject o = null;

            //try
            {
                // Create a request using a URL that can receive a get. 
                string addressString = "http://" + Ip.ToString() + ":" + Port.ToString() + inObject.Uri;
                HttpRequest request = new HttpRequest(addressString);
                request.Get();
                // Get the response.
                string response = request.ResponseToString();
                o = ParseGetResponse(response, inObject.Uri, inObject.Type);
            }
           /* catch (Exception)
            {
                string address = "http://" + Ip.ToString() + ":" + Port.ToString() + "/obix/batch/";

                AddErrorMessage("Can't connect to " + address);
            }*/

            return o;
        }

        public ObixObject ParseGetResponse(string response, string uri, string obixType)
        {            
            ObixObject o = null;
            XmlDocument xdoc = new XmlDocument();
            xdoc.LoadXml(response);
            XmlNodeList elemList = xdoc.GetElementsByTagName(obixType);

            if (elemList.Count == 0)
                elemList = xdoc.GetElementsByTagName("err");

            if (elemList.Count == 1)
            {
                XmlNode n = elemList[0];
                if (ObixObject.ObixTypesNames.ContainsValue(obixType) || ObixObject.ObixTypesNames.ContainsValue("err"))
                {
                    o = new ObixObject();
                    o.Type = n.Name;
                    string displayError = "";

                    foreach (XmlAttribute attrib in n.Attributes)
                    {
                        if (attrib.Name == "href")
                            o.Uri = attrib.Value;
                        else if (attrib.Name == "val")
                            o.Value = attrib.Value;
                        else if (attrib.Name == "display")
                            displayError = attrib.Value;
                    }

                    if (o.Type == "err")
                    {
                        string error;
                        if (displayError == "")
                            error = "href: " + o.Uri + " " + o.Contract;
                        else
                            error = "href: " + o.Uri + " " + o.Contract + ": " + displayError;
                        AddErrorMessage(error);
                    }

                }
            }

            if (o != null && uri != o.Uri && o.Uri.EndsWith(uri) == false)
            {
                AddErrorMessage(Ip.ToString()+": Connection error");
                return null;
            }

            return o;
        }
    }

    
}
